package fr.insalyon.sims.broadcast.network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Hashtable;

/**
 * This class is a stream receiver for multicast, i.e. UDP protocol.
 * 
 * @author GONZALEZ Silvia, GOTTERO Jeremie, SATTAYAKAWEE Nutchanat
 */

public class MCastStreamReceiver extends BaseStreamReceiver {
	// ------------------------------------------------------- CONSTANTS -- //
	// ------------------------------------------------------ ATTRIBUTES -- //
	
	/**
	 * multicast socket listening to multicast server
	 */
	private MulticastSocket socket;
	
	/**
	 * group of multicast to be joined
	 */
	private InetAddress group;
	
	/**
	 * port of multicast server
	 */
	private int port;
	
	/**
	 * waited frame number to be sent
	 */
	private long waitedFrameNumber;
	
	/**
	 * buffer storing the frames arriving to this object
	 */
	private Hashtable<Long, byte[]> dataBuffer;
	
	// ---------------------------------------------------- CONSTRUCTORS -- //
	public MCastStreamReceiver(InetAddress group, int port) {
		this.group = group;
		this.port = port;
		waitedFrameNumber = 0;
		dataBuffer = new Hashtable<Long, byte[]>();
		
	}
	// --------------------------------------------------------- METHODS -- //
	
	// ------------------------------------------------- PRIVATE METHODS -- //
	
	/**
	 * Opens MCastStreamReceiver connection
	 * 
	 * @throws IOException if an I/O exception occurs while creating the MulticastSocket, or there is an error joining or when the address is not a multicast address
	 * @throws SocketException if there is an error in the underlying protocol, such as an UDP error
	 */
	public void connect() throws IOException, SocketException{
		socket = new MulticastSocket(port);
		socket.joinGroup(group);
		socket.setSoTimeout(10000);
	}

	/**
	 * Closes MCastStreamReceiver connection
	 * 
	 * @throws IOException if there is an error leaving or when the address is not a multicast address
	 */
	public void disconnect() throws IOException {
		socket.leaveGroup(group);
		socket.close();
	}
	
	// --------------------------------------------------- INNER CLASSES -- //

	protected void init() throws IOException {
		// No initialization job to do here
	}
	
	/**
	 * Reads the stream and sends the data to the its listeners. Maximum data for each reading is 2 KB.
	 * As the received data may not arrive in order, it is responsible for re-arranging the data frames in order for sending to the listeners.
	 */
	protected void readStream() throws IOException {
		// Read 2kB of data and 4 B of frame number on socket
		byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
		DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
		try {
			socket.receive(packet);
		} catch (SocketTimeoutException e) { 
			// Ignored
			return;
		} catch (SocketException e) {
			if (e.getMessage().indexOf("socket closed") == -1) // Bricolage...
				throw e;
			else
				return;
		}
		fireDataReceived(packet.getData(),
				packet.getOffset(), packet.getLength());
		
//		// Read 2kB of data and 4 B of frame number on socket
//		byte[] buffer = new byte[DEFAULT_BUFFER_SIZE + 8];
//		DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
//		try {
//			socket.receive(packet);
//		} catch (SocketTimeoutException e) { 
//			// Ignored
//			return;
//		} catch (SocketException e) {
//			if (e.getMessage().indexOf("socket closed") == -1) // Bricolage...
//				throw e;
//			else
//				return;
//		}
//		
//		// extract frame number and copy the real data to another array
//		long frameNumber = 0;
//		for (int i = 0; i < 8; i++)
//			frameNumber |= (buffer[i] & 0xff) << (8 * (7 - i));
//		
//		int length = packet.getLength() - 8;
//		byte[] data = new byte[length];
//		System.arraycopy(buffer, 8, data, 0, length);
//		
//		// send data to the listeners
//		if (waitedFrameNumber == frameNumber) {
//			fireDataReceived(data, 0, data.length );
//			while (dataBuffer.containsKey(++waitedFrameNumber)) {
//				byte[] sentData = dataBuffer.remove(waitedFrameNumber);
//				fireDataReceived(sentData, 0, sentData.length );
//			}
//		} else {
//			dataBuffer.put(frameNumber, data);
//		}
	}
	
	protected void cleanUp() {	
		// No clean up job to do here
	}
}
